--[[
* @author PureMVC LUA Native Port by Ravior 
* @author Copyright(c) 2015 Gitlib, Inc., Some rights reserved.
* 
* @class puremvc.Facade
* Facade exposes the functionality of the Controller, Model and View
* actors to client facing code. 
* 
* This Facade implementation is a Multiton, so you should not call the 
* constructor directly, but instead call the static Factory method, 
* passing the unique key for this instance to #getInstance
]]
local Model = import("...core.Model")
local View = import("...core.View")
local Controller = import("...core.Controller")
local Notification = import("..observer.Notification")

local Facade = class("Facade")


--[[
* @constructor
* @param {string} key
* 	The multiton key to use to retrieve the Facade instance.
* @throws {Error} 
*  If an attempt is made to instantiate Facade directly
]]
function Facade:ctor(key)
	if Facade.instanceMap[key] ~= nil then
		error(Facade.MULTITON_MSG)
	end
	self:initializeNotifier(key)
	Facade.instanceMap[key] = self
	self:initializeFacade()
end


--[[
* Initialize the Multiton Facade instance.
* 
* Called automatically by the constructor. Override in your subclass to any
* subclass specific initializations. Be sure to call the 'super' 
* initializeFacade method, though
* 
*     MyFacade.prototype.initializeFacade= function ()
*     {
*         Facade.call(this);
*     };
* @protected
* @return {void}
]]
function Facade:initializeFacade()
	self:initializeModel()
	self:initializeController()
	self:initializeView()
end


--[[
* Facade Multiton Factory method. 
* Note that this method will return null if supplied a
* null or undefined multiton key.
* 
* @param {string} key
* 	The multiton key use to retrieve a particular Facade instance
* @return {puremvc.Facade}
]]
function Facade:getInstance(key)
	if nil == key then 
		return nil
	end

	if Facade.instanceMap[key] == nil then
		Facade.instanceMap[key] = Facade.new(key)
	end

	return Facade.instanceMap[key]
end


--[[
* Initialize the {@link puremvc.Controller Controller}.
* 
* Called by the #initializeFacade method.
* 
* Override this method in your subclass of Facade
* if one or both of the following are true:
* - You wish to initialize a different Controller
* - You have 
* {@link puremvc.SimpleCommand SimpleCommand}s
* or {@link puremvc.MacroCommand MacroCommand}s
* to register with the Controllerat startup.   
* 
* If you don't want to initialize a different Controller, 
* call the 'super' initializeControlle method at the beginning of your
* method, then register commands.
* 
*     MyFacade.prototype.initializeController= function ()
*     {
*         Facade.prototype.initializeController.call(this);
*         this.registerCommand(AppConstants.A_NOTE_NAME, ABespokeCommand)
*     }
* 
* @protected
* @return {void}
]]
function Facade:initializeController()
	if self.controller ~= nil then
		return
	end

	self.controller = Controller:getInstance(self.multitonKey)
end


--[[
* @protected
* Initialize the {@link puremvc.Model Model};
* 
* Called by the #initializeFacade method.
* Override this method in your subclass of Facade if one of the following are
* true:
* 
* - You wish to initialize a different Model.
* 
* - You have {@link puremvc.Proxy Proxy}s to 
*   register with the Model that do not retrieve a reference to the Facade at 
*   construction time.
* 
* If you don't want to initialize a different Model
* call 'super' #initializeModel at the beginning of your method, then register 
* Proxys.
* 
* Note: This method is*rarely* overridden; in practice you are more
* likely to use a command to create and registerProxys with the Model>, 
* since Proxys with mutable data will likely
* need to send Notifications and thus will likely want to fetch a reference to 
* the Facade during their construction. 
* 
* @return {void}
]]
function Facade:initializeModel()
	if self.model ~= nil then
		return
	end

	self.model = Model:getInstance(self.multitonKey)
end


--[[
* @protected
* 
* Initialize the {@link puremvc.View View}.
* 
* Called by the #initializeFacade method.
* 
* Override this method in your subclass of Facade if one or both of the 
* following are true:
*
* - You wish to initialize a different View.
* - You have Observers to register with the View
* 
* If you don't want to initialize a different View 
* call 'super' #initializeView at the beginning of your
* method, then register Mediator instances.
* 
*     MyFacade.prototype.initializeView= function ()
*     {
*         Facade.prototype.initializeView.call(this);
*         this.registerMediator(new MyMediator());
*     };
* 
* Note: This method is*rarely* overridden; in practice you are more
* likely to use a command to create and register Mediators
* with the View, since Mediator instances will need to send 
* Notifications and thus will likely want to fetch a reference 
* to the Facade during their construction. 
* @return {void}
]]
function Facade:initializeView()
	if self.view ~= nil then
		return
	end

	self.view = View:getInstance(self.multitonKey)
end


--[[
* Register a command with the Controller by Notification name
* @param {string} notificationName
*  The name of the Notification to associate the command with
* @param {Function} commandClassRef
*  A reference ot the commands constructor.
* @return {void}
]]
function Facade:registerCommand(notificationName,commandClassRef)
	self.controller:registerCommand(notificationName,commandClassRef)
end


--[[
* Remove a previously registered command to Notification mapping from the
* {@link puremvc.Controller#removeCommand Controller}
* @param {string} notificationName
*  The name of the the Notification to remove from the command mapping for.
* @return {void}
]]
function Facade:removeCommand(notificationName)
	self.controller:removeCommand(notificationName)
end


--[[
* Check if a command is registered for a given notification.
* 
* @param {string} notificationName
*  A Notification name
* @return {boolean}
*  Whether a comman is currently registered for the given notificationName
]]
function Facade:hasCommand(notificationName)
	return self.controller:hasCommand(notificationName)
end


--[[
* Register a Proxy with the {@link puremvc.Model#registerProxy Model}
* by name.
* 
* @param {puremvc.Proxy} proxy
*  The Proxy instance to be registered with the Model.
* @return {void}
]]
function Facade:registerProxy(proxy)
	self.model:registerProxy(proxy)
end


--[[
* Retrieve a Proxy from the Model
* 
* @param {string} proxyName
* @return {puremvc.Proxy}
]]
function Facade:retrieveProxy(proxyName)
	return self.model:retrieveProxy(proxyName)
end


--[[
* Remove a Proxy from the Model by name
* @param {string} proxyName
*  The name of the Proxy
* @return {puremvc.Proxy}
*  The Proxy that was removed from the Model
]]
function Facade:removeProxy(proxyName)
	local proxy = nil
	if self.model ~= nil then
		proxy = self.model:removeProxy(proxyName)
	end

	return proxy
end


--[[
* Check it a Proxy is registered.
* @param {string} proxyName
*  A Proxy name
* @return {boolean}
*  Whether a Proxy is currently registered with the given proxyName
]]
function Facade:hasProxy(proxyName)
	return self.model:hasProxy(proxyName)
end


--[[
* Register a Mediator with with the View.
* 
* @param {puremvc.Mediator} mediator
*  A reference to the Mediator to register
* @return {void}
]]
function Facade:registerMediator(mediator)
	if self.view ~= nil then
		self.view:registerMediator(mediator)
	end
end


--[[
* Retrieve a Mediator from the View by name
* 
* @param {string} mediatorName
*  The Mediators name
* @return {puremvc.Mediator}
*  The retrieved Mediator
]]
function Facade:retrieveMediator(mediatorName)
	return self.view:retrieveMediator(mediatorName)
end


--[[
* Remove a Mediator from the View.
* 
* @param {string} mediatorName
*  The name of the Mediator to remove.
* @return {puremvc.Mediator}
*  The removed Mediator
]]
function Facade:removeMediator(mediatorName)
	local mediator = nil
	if self.view ~= nil then
		mediator = self.view.removeMediator(mediatorName)
	end

	return mediator
end


--[[
* Check if a Mediator is registered or not.
* 
* @param {string} mediatorName
*  A Mediator name
* @return {boolean}
*  Whether a Mediator is registered with the given mediatorName
]]
function Facade:hasMediator(mediatorName)
	return self.view:hasMediator(mediatorName)
end


--[[
* Create and send a 
* {@link puremvc.Notification Notification}
* 
* Keeps us from having to construct new Notification instances in our
* implementation
* 
* @param {string} notificationName
*  The name of the Notification to send
* @param {Object} [body]
*  The body of the notification
* @param {string} [type]
*  The type of the notification
* @return {void}
]]
function Facade:sendNotification(notificationName,body,type)
	self:notifyObservers(Notification.new(notificationName,body,type))
end


--[[
* Notify {@link puremvc.Observer Observer}s
* 
* This method is left public mostly for backward compatibility, and to allow
* you to send custom notification classes using the facade.
* 
* Usually you should just call sendNotification and pass the parameters, never 
* having to construct the notification yourself.
* 
* @param {puremvc.Notification} notification
*  The Notification to send
* @return {void}
]]
function Facade:notifyObservers(notification)
	if self.view ~= nil then
		self.view.notifyObservers(notification)
	end
end


--[[
* Initialize the Facades Notifier capabilities by setting the Multiton key for 
* this facade instance.
* 
* Not called directly, but instead from the constructor when #getInstance is 
* invoked. It is necessary to be public in order to implement Notifier
* 
* @param {string} key
* @return {void}
]]
function Facade:initializeNotifier(key)
	self.multitonKey = key
end


--[[
* Check if a*Core* is registered or not
*
* @static
* @param {string} key
*  The multiton key for the*Core* in question
* @return {boolean}
*  Whether a*Core* is registered with the given key
]]
function Facade:hasCore(key)
	return Facade.instanceMap[key] ~= nil
end


--[[
* Remove a*Core* 
* 
* Remove the Model, View, Controller and Facade for a given key.
*
* @static
* @param {string} key
* @return {void}
]]
function Facade:removeCore(key)
	if Facade.instanceMap[key] == nil then
		return
	end

	Model:removeModel(key)
	View:removeView(key)
	Controller:removeController(key)
	Facade.instanceMap[key] = nil
end


--[[
* @ignore
* The Multiton Facade instance map.
* @static
* @protected
* @type Array
]]
Facade.instanceMap = {}


--[[
* @ignore
* Message Constants
* @protected
* @type {string}
* @const
* @static
]]
Facade.MULTITON_MSG = "Facade instance for this Multiton key already constructed!";

return Facade